package cate.game.pvp.pointrace.handler;

import cate.common.table.d.GDEliteRace;
import cate.game.pvp.pointrace.data.*;
import cate.game.rank.RankAdmireConsumer;
import easy.java.practice.errorcode.EcResult;
import org.apache.commons.lang3.StringUtils;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;

import java.util.*;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.function.Supplier;

import static cate.game.pvp.pointrace.data.PointRaceRank.INDEX_NAME;

public class BothPointRaceRankHandler implements PointRaceRankHandler {

    private final PointRaceRankData rankData;

    private final PointRacePsOperation psOperation;
    private final RankAdmireConsumer consumer;

    private final Map<String, Integer> rankIndexMap = new HashMap<>();

    public BothPointRaceRankHandler(Supplier<PointRaceRankData> rankDataSupplier,
                                    PointRacePsOperation psOperation,
                                    RankAdmireConsumer consumer) {
        Objects.requireNonNull(rankDataSupplier);
        this.rankData = Objects.requireNonNull(rankDataSupplier.get());
        this.psOperation = Objects.requireNonNull(psOperation);
        this.consumer = consumer;
        sortRankThenUpdateIndex();
    }

    @Override
    public EcResult<Void> admire(String uid, int rank) {
        EcResult<Void> r = new EcResult<>();
        if (rank < 0 || rank >= rankData.size()) {
            return r.fail("超出排行榜范围");
        }
        PointRaceRank roleRank = rankData.getRankData(rank);
        if (Objects.isNull(roleRank)) {
            return r.fail("查不到此人数据");
        }
        if (StringUtils.equalsIgnoreCase(roleRank.getOwner().uid, uid)) {
            return r.fail("无法给自己点赞");
        }
        roleRank.admireNum += 1;

        Optional.ofNullable(this.consumer)
                .ifPresent(c -> c.consume(roleRank.owner.base.srvId, roleRank.getUid(), getRankId()));

        return r;
    }

    @Override
    public int getIndex(String uid) {
        return rankIndexMap.getOrDefault(uid, -1);
    }

    @Override
    public PointRaceRank getData(String uid) {
        return rankData.getIndex(INDEX_NAME).get(uid);
    }

    @Override
    public Flux<PointRaceRank> getRankFlow() {
        return Flux.fromIterable(rankData.getValueList());
    }

    @Override
    public Flux<PointRacePs> getRankPsFlow() {
        return getRankFlow().map(PointRaceRank::getUid)
                .map(uid -> Optional.ofNullable(this.psOperation.findOne(uid)))
                .filter(Optional::isPresent)
                .map(Optional::get)
                .take(GDEliteRace.RANK_LIMIT)
                .onErrorResume(e -> Mono.empty());
    }

    public void sortRankThenUpdateIndex() {
        rankData.getValueList()
                .sort(Comparator.comparing(PointRaceRank::getValue)
                        .reversed()
                        .thenComparing(PointRaceRank::getRecordTime));

        AtomicInteger counter = new AtomicInteger(0);

        rankData.getValueList()
                .forEach(rank -> rankIndexMap.put(rank.getUid(), counter.incrementAndGet()));
    }

    @Override
    public int update(PointRaceOpponent opponent, long newPoints) {
        if (rankData.size() >= getRankLimit()) {
            PointRaceRank lowestRank = rankData.getRankData(getRankLimit() - 1);
            if (lowestRank.getValue() > newPoints) {
                return -1;
            }
            rankData.getValueList().remove(rankData.size() - 1);
            rankData.getIndex(INDEX_NAME).remove(lowestRank.getIndexName());
        }

        PointRaceRank rank = getData(opponent.getSnapshot().uid);
        if (Objects.nonNull(rank)) {
            rank.value = newPoints;
            // 更新玩家最新的快照，同步快照修改
            rank.owner = opponent.getSnapshot().copy();
            rank.recordTime = System.currentTimeMillis();
        } else {
            rank = PointRaceRank.of(opponent, newPoints);
            rankData.addValue(rank);
            rankData.addIndex(INDEX_NAME, rank.getIndexName(), rank);
        }

        sortRankThenUpdateIndex();

        return getIndex(opponent.getSnapshot().uid);
    }

    @Override
    public void clear() {
        this.rankIndexMap.clear();
    }
}
